#ifndef __GAMEINC_H__
#define __GAMEINC_H__


#include <iostream>
#include <format>
#include <memory>
#include <vector>
#include <utility>

class GamePiece
{
public:
    virtual ~GamePiece() = default;
    virtual std::unique_ptr<GamePiece> clone() const = 0;
};

class ChessPiece : public GamePiece
{
    virtual std::unique_ptr<GamePiece> clone() const override
    {
        return std::make_unique<ChessPiece>(*this);
    }
};

class GameBoard
{
public:
    explicit GameBoard(size_t width = DefaultWidth, size_t height = DefaultHeight)
        : m_width{width},
          m_height{height}
    {
        m_cells.resize(m_width);
        for (auto &column : m_cells)
        {
            column.resize(m_height);
        }
    }

    virtual ~GameBoard() = default;

    GameBoard(const GameBoard &src)
        : GameBoard{src.getWidth(), src.getHeight()}
    {
        for (size_t i{0}; i < m_width; ++i)
        {
            for (size_t j = 0; j < m_height; ++j)
            {
                if (src.m_cells[i][j] != nullptr)
                {
                    m_cells[i][j] = src.m_cells[i][j]->clone();
                }
            }
        }
    }

    GameBoard &operator=(const GameBoard &rhs)
    {
        GameBoard temp{rhs};
        swap(temp);

        return *this;
    }

    // Explicitly default a move constructor and assignment operator.
    GameBoard(GameBoard &&src) = default;
    GameBoard &operator=(GameBoard &&rhs) = default;

    std::unique_ptr<GamePiece> &at(size_t x, size_t y)
    {
        verifyCoordinate(x, y);
        return m_cells[x][y];
    }

    const std::unique_ptr<GamePiece> &at(size_t x, size_t y) const
    {
        return const_cast<std::unique_ptr<GamePiece> &>(std::as_const(*this).at(x, y));
    }

    size_t getHeight() const
    {
        return m_height;
    }

    size_t getWidth() const
    {
        return m_width;
    }

    static const size_t DefaultWidth{10};
    static const size_t DefaultHeight{10};

    void swap(GameBoard &other) noexcept
    {
        std::swap(m_width, other.m_width);
        std::swap(m_height, other.m_height);
        std::swap(m_cells, other.m_cells);
    }

    //
    void draw()
    {
        for (size_t i{0}; i < m_width; ++i)
        {
            for (size_t j{0}; j < m_height; ++j)
            {
                std::cout << std::format(
                    "{}({}*{}) ", m_cells[i][j] != nullptr ? 1 : 0, i, j);
            }
            std::cout << std::endl;
        }
    }

private:
    void verifyCoordinate(size_t x, size_t y) const
    {
        if (x >= m_width)
        {
            throw std::out_of_range{std::format("{} must be less than {}.", x, m_width)};
        }

        if (y >= m_height)
        {
            throw std::out_of_range{std::format("{} must be less than {}.", y, m_height)};
        }
    }

    std::vector<std::vector<std::unique_ptr<GamePiece>>> m_cells;
    size_t m_width;
    size_t m_height;
};

void swap(GameBoard &first, GameBoard &second) noexcept
{
    first.swap(second);
}

#endif
